package pilhun.collections;

import java.util.AbstractCollection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;



public class MyLinkedList implements MyList, MyLinkList, MyQueue, MyStack{
	
	private Node firstNode;
	private Node lastNode;
	private int size;
	private int globalCounter=0;
	
	private class Node{
		private Node nextNode;
		private Node previousNode;
		private Object content;
		
		public Node(Node previousNode, Node nextNode, Object content) {
			this.nextNode = nextNode;
			this.previousNode = previousNode;
			this.content = content;
		}
		public Node getNextNode() {
			return nextNode;
		}
		public void setNextNode(Node nextNode) {
			this.nextNode = nextNode;
		}
		public Node getPreviousNode() {
			return previousNode;
		}
		public void setPreviousNode(Node previousNode) {
			this.previousNode = previousNode;
		}
		public Object getContent() {
			return content;
		}
		public void setContent(Object content) {
			this.content = content;
		}
		
	}
	
	private class IteratorImpl implements Iterator{
		private Node nextNode;
		private Node previousNode;
		private Node lastReturned;
		private int counter;
		
		
		public IteratorImpl() {
			setPreviousNode(null);
			setNextNode(getFirstNode());
			setCounter(globalCounter);
		}

		public boolean hasNext() {
			if (globalCounter != getCounter()){
				throw new ConcurrentModificationException();
			}
			if (getNextNode() == null){
				return false;
			}
			return true;
		}
		
		protected boolean hasPrevious(){
			if (globalCounter != getCounter()){
				throw new ConcurrentModificationException();
			}
			if (getPreviousNode() == null){
				return false;
			}
			return true;
		}

		public Object next() throws NoSuchElementException{
			/*if (getNextNode() == null){
				if (getFirstNode() == null){
					throw new NoSuchElementException();
				}
				setNode(getFirstNode());
			}*/
			if (globalCounter != getCounter()){
				throw new ConcurrentModificationException();
			}
			if (!hasNext()){
				throw new NoSuchElementException();
			}
			Node node = getNextNode();
			setPreviousNode(node);
			setNextNode(node.getNextNode());
			setLastReturned(node);
			return node.getContent();
		}
		
		protected Object previous(){
			if (globalCounter != getCounter()){
				throw new ConcurrentModificationException();
			}
			if (!hasPrevious()){
				throw new NoSuchElementException();
			}
			Node node = getPreviousNode();
			setPreviousNode(node.getPreviousNode());
			setNextNode(node);
			setLastReturned(node);
			return node.getContent();
		}
		private void linkNodes(Node node1, Node node2){
			if (node1 != null){
				node1.setNextNode(node2);
			}
			if (node2 !=null){
				node2.setPreviousNode(node1);
			}
		}
		private void checkFirstLast(Node removedNode){
			if (removedNode == getFirstNode()){
				setFirstNode(removedNode.getNextNode());
			}else{
				if (removedNode == getLastNode()){
					setLastNode(removedNode.getPreviousNode());
				}
			}
		}
		
		public void remove() {
			if (globalCounter != getCounter()){
				throw new ConcurrentModificationException();
			}
			if (getLastReturned() == null){
				throw new IllegalStateException();
			}
			
			if (getLastReturned() == getPreviousNode()){ //Previous command was "next"
				Node node = getPreviousNode().getPreviousNode();
				setPreviousNode(node);
				linkNodes(node, getNextNode());
			}else{
				Node node = getNextNode().getNextNode();
				setNextNode(node);
				linkNodes(getPreviousNode(), node);
			}
			checkFirstLast(getLastReturned());
			setLastReturned(null);
			size--;
			setCounter(getCounter()+1);
			globalCounter++;		
		}
		
		protected int getCounter() {
			return counter;
		}

		protected void setCounter(int counter) {
			this.counter = counter;
		}

		protected Node getNextNode() {
			return nextNode;
		}
		protected void setNextNode(Node nextNode) {
			this.nextNode = nextNode;
		}
		
		protected Node getPreviousNode() {
			return previousNode;
		}
		protected void setPreviousNode(Node previousNode) {
			this.previousNode = previousNode;
		}
		
		protected Node getLastReturned() {
			return lastReturned;
		}
		protected void setLastReturned(Node lastReturned) {
			this.lastReturned = lastReturned;
		}
		
		
	}
	private class ListIteratorImpl extends IteratorImpl implements ListIterator{
		private int ind = 0;

		public ListIteratorImpl(){
			super();
		}
		public ListIteratorImpl(int ind) {
			
			checkBounds(ind);
			Node node;
			if (ind <= size/2){
				node = getFirstNode();
				for (int i = 0; i<ind; i++){
					node = node.getNextNode();
				}
				setNextNode(node);
				if (node != null){
					setPreviousNode(node.getPreviousNode());
				}else{
					setPreviousNode(null);
				}
			}else{
				node = getLastNode();
				for (int i = size; i>ind; i--){
					node = node.getPreviousNode();
				}
				setPreviousNode(node);
				setNextNode(node.getNextNode());
			}
			this.ind = ind;
		}
		
		private void checkBounds (int ind){
			if (ind < 0){
				throw new IndexOutOfBoundsException();
			}
			if (ind>size){
				throw new IndexOutOfBoundsException();
			}
		}
		private void changeFirstLast(Node node){
			if (node.getPreviousNode() == getLastNode()){
				setLastNode(node);
			}
			if (node.getNextNode() == getFirstNode()){
				setFirstNode(node);
			}
		}
		public void add(Object e) {
			if (globalCounter != getCounter()){
				throw new ConcurrentModificationException();
			}
			Node newNode = new Node(getPreviousNode(), getNextNode(), e);
			if (hasPrevious()){
				Node previous = getPreviousNode();
				previous.setNextNode(newNode);
				setPreviousNode(newNode);
			}else{
				setPreviousNode(newNode);
			}
			if (hasNext()){
				getNextNode().setPreviousNode(newNode);
			}
			changeFirstLast(newNode);
			ind++;
			size++;
			setCounter(getCounter()+1);
			globalCounter++;
		}
		@Override
		public boolean hasPrevious() {
			return super.hasPrevious();
		}
		@Override
		public Object next() {
			ind++;
			return super.next();
		}
		
		public int nextIndex() {

			return ind;
		}

		public Object previous() {
			ind--;
			return super.previous();
		}

		public int previousIndex() {

			return ind-1;
		}
		@Override
		public void remove() {
			if (globalCounter != getCounter()){
				throw new ConcurrentModificationException();
			}
			super.remove();
			ind--;
		}
		
		public void set(Object o) {
			if (globalCounter != getCounter()){
				throw new ConcurrentModificationException();
			}
			if (getLastReturned() == null){
				throw new IllegalStateException();
			}
			getLastReturned().setContent(o);  
		}
		
	}
	
	private class DescendingIteratorImpl extends ListIteratorImpl implements Iterator{
		public DescendingIteratorImpl() {
			setPreviousNode(getLastNode());
			setNextNode(null);
			setCounter(globalCounter);
		}
		@Override
		public boolean hasNext() {
			return super.hasPrevious();
		}

		public Object next() {
			return super.previous();
		}

	}
	public void add(Object e) {
		ListIterator liter = listIterator(size);
		liter.add(e);
	}

	public void add(int index, Object e) {
		ListIterator liter = listIterator(index);
		liter.add(e);
	}

	public void addAll(Object[] c) {	
		for (Object e:c){
			add(e);
		}
	}

	public void addAll(int index, Object[] c) {
		for (int i = c.length-1;i>=0;i--){
			add(index, c[i]);
		}
	}


	public Object get(int index) {
		ListIterator liter = listIterator(index);
		return liter.next();
	}

	public Object remove(int index) {
		ListIterator liter = listIterator(index);
		Object obj = liter.next();
		liter.remove();
		return obj;
	}

	public void clear() {
		setFirstNode(null);
		setLastNode(null);
		size = 0;
	}

	public boolean isEmpty() {
		return size > 0;
	}

	public void set(int index, Object e) {
		ListIterator liter = listIterator(index);
		liter.next();
		liter.set(e);
	}

	public int indexOf(Object o) {//потестить
		ListIterator iter = listIterator();
		while (iter.hasNext()){
			 if (o == iter.next()){
				 return iter.previousIndex();
			 }
		}
		return -1;
	}

	public int size() {
		return size;
	}

	public Object[] toArray() {
		Object[] objArr = new Object[size];
		Iterator iter = iterator();
		for (int i = 0; i<size;i++){
			objArr[i] = iter.next();
		}
		return objArr;
	}

	public Iterator iterator() {
		return new IteratorImpl();
	}
	
	public ListIterator listIterator(){
		return new ListIteratorImpl();
	}
	
	public ListIterator listIterator(int ind){
		return new ListIteratorImpl(ind);
	}
	

	@Override
	public String toString() {
		String s = "[";
		StringBuilder sb = new StringBuilder(s);
		ListIterator liter = this.listIterator();
		if (!liter.hasNext()){
			return "[]";
		}
		while(liter.hasNext()){
			sb.append(liter.next().toString());
			sb.append(", ");
		}
		s = sb.substring(0, sb.length()-2)+"]";
		return s;
	}

	public void addFirst(Object e) {
		ListIterator liter = listIterator();
		liter.add(e);
	}
	
	public void addLast(Object e) {
		ListIterator liter = listIterator(size);
		liter.add(e);
	}
	
	public Object getFirst() {
		return get(0);
	}
	
	public Object getLast() {
		return get(size-1);
	}
	
	public Object removeFirst() {
		return remove(0);
	}
	
	public Object removeLast() {
		return remove(size-1);
	}
	
	public Iterator descendingIterator() {
		return new DescendingIteratorImpl();
	}
	private Node getFirstNode() {
		return firstNode;
	}

	private void setFirstNode(Node firstNode) {
		this.firstNode = firstNode;
	}

	private Node getLastNode() {
		return lastNode;
	}

	private void setLastNode(Node lastNode) {
		this.lastNode = lastNode;
	}

	public void push(Object e) {
		addLast(e);
		
	}

	public Object pop() {
		return removeLast();
	}

	public void offer(Object e) {
		addLast(e);
		
	}

	public Object peek() {
		return getFirst();
	}

	public Object poll() {
		return removeFirst();
	}


}
